package com.region.spring.boot;

import com.region.core.env.listener.PropertyRefreshListener;
import com.region.http.client.config.ClientConfig;
import com.region.http.client.config.CustomClientConfigurer;
import com.region.http.client.protector.RequestProtector;
import com.region.http.client.request.HttpExecutor;
import com.region.http.client.resolver.parameter.ParameterResolver;
import com.region.http.client.resolver.result.ResultResolver;
import com.region.plugin.Interceptor;
import com.region.spring.RegionClientFactoryBean;
import com.region.spring.annotation.RegionConfigurerRegistrar;
import com.region.spring.client.RegionConfigurer;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.boot.autoconfigure.AutoConfigurationPackages;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.util.ObjectUtils;

import java.util.Arrays;
import java.util.List;

/**
 * @author liujieyu
 * @date 2024/7/17 20:12
 * @desciption
 */
@Configuration
@ConditionalOnClass({ClientConfig.class, RegionClientFactoryBean.class})
@AutoConfigureAfter({TaskExecutionAutoConfiguration.class})
@EnableConfigurationProperties(RegionAdapterProperties.class)
@ConditionalOnProperty(name = "region.client.enabled", havingValue = "true", matchIfMissing = true)
public class RegionClientAutoConfiguration {

    private final RegionAdapterProperties properties;

    private final Interceptor[] interceptors;

    private final ParameterResolver[] parameterResolvers;

    private final ResultResolver[] resultResolvers;

    private final PropertyRefreshListener[] listeners;

    private final HttpExecutor[] httpExecutors;

    private final CustomClientConfigurer[] customClientConfigurers;

    private final RequestProtector[] requestProtectors;

    public RegionClientAutoConfiguration(RegionAdapterProperties properties, ObjectProvider<Interceptor[]> interceptorProvider,
                                         ObjectProvider<ParameterResolver[]> parameterResolverProvider,
                                         ObjectProvider<ResultResolver[]> resultResolverProvider,
                                         ObjectProvider<PropertyRefreshListener[]> listenerProvider,
                                         ObjectProvider<HttpExecutor[]> httpExecutorProvider,
                                         ObjectProvider<CustomClientConfigurer[]> customClientConfigurerProvider,
                                         ObjectProvider<RequestProtector[]> requestProtectorProvider) {
        this.properties = properties;
        this.interceptors = interceptorProvider.getIfAvailable();
        this.parameterResolvers = parameterResolverProvider.getIfAvailable();
        this.resultResolvers = resultResolverProvider.getIfAvailable();
        this.listeners = listenerProvider.getIfAvailable();
        this.httpExecutors = httpExecutorProvider.getIfAvailable();
        this.customClientConfigurers = customClientConfigurerProvider.getIfAvailable();
        this.requestProtectors = requestProtectorProvider.getIfAvailable();
    }

    @Bean
    @ConditionalOnMissingBean
    public ClientConfig regionClientConfig() {
        ClientConfig.ClientConfigBuilder builder = ClientConfig
                .builder()
                .loggerAdapter(properties.getLoggerAdapter())
                .jsonAdapter(properties.getJsonAdapter())
                .loadBalancerConfig(properties.getBalancerConfig())
                .globalRequestConfig(properties.getGlobal())
                .cache(properties.getCachePath())
                .autoMonitor(properties.isAutoMonitor());
        return builder.build();
    }

    @Bean
    @ConditionalOnMissingBean
    public RegionClientFactoryBean regionClientFactoryBean(@Autowired ClientConfig regionClientConfig,
                                                           @Autowired(required = false) ThreadPoolTaskExecutor taskExecutor) {
        RegionClientFactoryBean regionClientFactoryBean = new RegionClientFactoryBean(regionClientConfig);
        if (!ObjectUtils.isEmpty(interceptors)) {
            regionClientFactoryBean.setInterceptors(Arrays.asList(interceptors));
        }
        if (!ObjectUtils.isEmpty(parameterResolvers)) {
            regionClientFactoryBean.setParameterResolvers(Arrays.asList(parameterResolvers));
        }
        if (!ObjectUtils.isEmpty(resultResolvers)) {
            regionClientFactoryBean.setResultResolvers(Arrays.asList(resultResolvers));
        }
        if (!ObjectUtils.isEmpty(listeners)) {
            regionClientFactoryBean.setRefreshListeners(Arrays.asList(listeners));
        }
        if (!ObjectUtils.isEmpty(httpExecutors)) {
            regionClientFactoryBean.setHttpExecutors(Arrays.asList(httpExecutors));
        }
        if (!ObjectUtils.isEmpty(customClientConfigurers)) {
            regionClientFactoryBean.setCustomClientConfigurers(Arrays.asList(customClientConfigurers));
        }
        if (!ObjectUtils.isEmpty(requestProtectors)) {
            regionClientFactoryBean.setRequestProtectors(Arrays.asList(requestProtectors));
        }
        if (taskExecutor != null) {
            regionClientFactoryBean.setPoolExecutor(taskExecutor.getThreadPoolExecutor());
        }
        return regionClientFactoryBean;
    }

    @ConditionalOnClass({ClientConfig.class, RegionClientFactoryBean.class})
    public static class AutoConfiguredRegionConfigurerRegistrar implements BeanFactoryAware, EnvironmentAware, ImportBeanDefinitionRegistrar {

        private BeanFactory beanFactory;

        private Environment environment;

        @Override
        public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
            List<String> packages = AutoConfigurationPackages.get(this.beanFactory);
            String[] property = environment.getProperty("region.client.base-packages", String[].class);
            if (property != null) {
                for (String s : property) {
                    packages.add(s);
                }
            }
            BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(RegionConfigurer.class);
            builder.addPropertyValue("basePackages", packages);
            registry.registerBeanDefinition(generateBaseBeanName(importingClassMetadata, 1), builder.getBeanDefinition());
        }

        private String generateBaseBeanName(AnnotationMetadata importingClassMetadata, int index) {
            return importingClassMetadata.getClassName() + "#" + RegionConfigurerRegistrar.class.getSimpleName() + "#" + index;
        }
        @Override
        public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
            this.beanFactory = beanFactory;
        }

        @Override
        public void setEnvironment(Environment environment) {
            this.environment = environment;
        }
    }

    @Configuration
    @Import({AutoConfiguredRegionConfigurerRegistrar.class})
    @ConditionalOnProperty(name = "region.client.enabled", havingValue = "true", matchIfMissing = true)
    public static class AutoConfiguredBeanDefinitionConfiguration {}
}
